/*
 * Copyright 2009 Red Hat, Inc.
 *
 * Red Hat licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
/**
 * Simplistic telnet client.
 *
 * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
 * @author <a href="http://gleamynode.net/">Trustin Lee</a>
 *
 * @version $Rev: 2080 $, $Date: 2010-01-26 18:04:19 +0900 (Tue, 26 Jan 2010) $
 */

package de.tuberlin.dima.dkey.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;



public class Client {

	static Logger logger = Logger.getLogger(Client.class);

	
    ChannelFuture lastWriteFuture = null;
    Channel channel = null;
    ClientBootstrap bootstrap = null;
	
    public Client(String host, int port) throws IOException {
    	if (!this.connect(host, port)){
    		throw new IOException("Could not connect");
    	}
	}

	public boolean connect(String host, int port) throws IOException {

        // Configure the client.
        bootstrap = new ClientBootstrap(
                new NioClientSocketChannelFactory(
                        Executors.newCachedThreadPool(),
                        Executors.newCachedThreadPool()));

        // Configure the pipeline factory.
        bootstrap.setPipelineFactory(new ClientPipelineFactory());

        // Start the connection attempt.
        ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));

        // Wait until the connection attempt succeeds or fails.
        channel = future.awaitUninterruptibly().getChannel();
        if (!future.isSuccess()) {
            future.getCause().printStackTrace();
            bootstrap.releaseExternalResources();
            return false;
        }else{
        	return true;
        }

    }
	
	private BlockingQueue<String> sendMessage(String message) throws IOException, InterruptedException{

            // Sends the received line to the server.
            lastWriteFuture = channel.write(message + "\r\n");
            
            return ((ClientHandler)channel.getPipeline().get("handler")).getMessages();
	}
	
	public BlockingQueue<String> getReturnQueue(){
		return ((ClientHandler)channel.getPipeline().get("handler")).getMessages();
	}
	
	public boolean end() throws IOException, InterruptedException{
		
		//Tell the server we are finished
		this.sendMessage("{\"command\": \"end\"}");
		

		//TODO: We could check here that the server returned bye 
		//but not really important
		
		channel.getCloseFuture().awaitUninterruptibly();
		
        // Wait until all messages are flushed before closing the channel.
        if (lastWriteFuture != null) {
            lastWriteFuture.awaitUninterruptibly();
        }

        // Close the connection.  Make sure the close operation ends because
        // all I/O operations are asynchronous in Netty.
        channel.close().awaitUninterruptibly();

        // Shut down all thread pools to exit.
        bootstrap.releaseExternalResources();
        
        return true;
	}
	
	
	
	/**
	 * *****************************************************************************
	 * 		The helper functions
	 * *****************************************************************************
	 */
	
	public boolean addTree(GTree tree) throws IOException{
		if(!channel.isOpen()){
			throw new IOException();
		}
		
		//Create the JSON to send
		StringBuffer toSend = new StringBuffer();
		toSend.append("{\"command\": \"newtree\", \"data\":" );
		toSend.append(tree.toString());
		toSend.append("}");
		
		//And send
		try {
			this.sendMessage(toSend.toString());
			
		} catch (InterruptedException e) {
			logger.error("There was an error in sending your createTree to the DB", e.getCause());
			return false;
		}
		
		return true;
	}
	
	public boolean commit() throws IOException{
		if(!channel.isOpen()){
			throw new IOException();
		}
		
		//And send
		try {
			this.sendMessage("{\"command\": \"commit\"}");
		} catch (InterruptedException e) {
			logger.error("There was an error in sending your createTree to the DB", e.getCause());
			return false;
		}
		
		return true;
	}

	public boolean query(GTree query) throws IOException {
		if(!channel.isOpen()){
			throw new IOException();
		}
		
		//Create the JSON to send
		StringBuffer toSend = new StringBuffer();
		toSend.append("{\"command\": \"search\", \"data\":" );
		toSend.append(query.toString());
		toSend.append("}");
		
		//And send
		try {
			this.sendMessage(toSend.toString());
		} catch (InterruptedException e) {
			logger.error("There was an error in sending your search to the DB", e.getCause());
			return false;
		}
		
		return true;		
		
	}
	
	
	public boolean get(Integer treeId) throws IOException {
		if(!channel.isOpen()){
			throw new IOException();
		}
		
		//Create the JSON to send
		StringBuffer toSend = new StringBuffer();
		toSend.append("{\"command\": \"get\", \"data\":[" );
		toSend.append(treeId);
		toSend.append("]}");
		
		//And send
		try {
			this.sendMessage(toSend.toString());
		} catch (InterruptedException e) {
			logger.error("There was an error in sending your search to the DB", e.getCause());
			return false;
		}
		
		return true;		
		
	}
	
}
